using System;
using System.Threading;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using remjobnet;

namespace client
{
    public class ClientController
    {
        private ApplicationManager am;
        public BackgroundWorker bw;
        private CommandListener cl;
        private Dictionary<ushort, ReceiveUDP> transferPending;
        private LogClient lc;
        
        public ClientController(ApplicationManager am)
        {
            this.am = am;
            cl = new CommandListener(this.tcpHandler, this.udpHandler);
            bw = cl.getBackgroundWorker();
            transferPending = new Dictionary<ushort, ReceiveUDP>();
            operations = new List<ushort>();
            lc = am.logger;
            bw.RunWorkerAsync();
        }

        public void doStop(Packet p)
        {
            PApplication pa = (PApplication)p;
            Application a = am.getApplication(pa.id_application);

            if (a == null)
            {
                lc.log("Application not found");
                return;
            }

            a.stop();
        }

        public void sendAck(Packet p, Socket s, IPEndPoint ipe)
        {
            Packet ack = new Packet(Command.ACK, p.id_operation, am.idClient, null);
            
            PacketSerializer ps = new PacketSerializer(ack);
            ps.serialize();

            Action sendack = new SendAck(ack, s, ipe);
            BackgroundWorker bw = sendack.getBackgroundWorker();

            bw.RunWorkerAsync();
            
        }

        public void sendHello(Packet p, Socket s, IPEndPoint ipe)
        {
            List<ushort> groups = new List<ushort>();

            groups.Add(am.idGroup);
            
            Packet hello = new Packet(Command.HELLO, p.id_operation, am.idClient, groups);

            PacketSerializer ps = new PacketSerializer(hello);
            ps.serialize();

            Action sendhello = new SendAck(hello, s, ipe);
            BackgroundWorker bw = sendhello.getBackgroundWorker();

            bw.RunWorkerAsync();
            
        
        }

        public void doUninstall(Packet p)
        {
            PApplication pa = (PApplication)p;
            Application a = am.getApplication(pa.id_application);

            if (a == null)
            {
                lc.log("Application not found");
                return;
            }

            am.uninstallApplicaton(a);
                       
        }

        public void doStartTcp(Packet p, NetworkStream ns)
        {
            PFile pa = (PFile)p;

            Application a = am.getApplication(pa.id_application);
            if (a == null)
            {
                lc.log("Application not found");
                return;
            }

            ReceiveTCP rt = new ReceiveTCP(ns, a.InputPath);
            //riceve il file e lo salva
            try
            {
                rt.run();
                a.start();
            }
            catch (Exception e) 
            {
                lc.log(e.Message);
            }
            
        }

        private bool isMine(Packet p)
        {
            return p.id_groups.Contains(am.idGroup);
        }

        private void newTransfer(PFile pa, Socket s, IPEndPoint ipe, string filename, RunWorkerCompletedEventHandler rh)
        {
            PNack pn = new PNack(Command.NACK, pa.id_file_operation, am.idClient, null, (uint)0);
            PacketSerializer ps = new PacketSerializer(pn);

            ReceiveUDP ru = new ReceiveUDP(pa, pn, s, ipe, filename);

            BackgroundWorker bw = ru.getBackgroundWorker();
            bw.RunWorkerCompleted += rh;

            bw.RunWorkerAsync();

            transferPending.Add(pa.id_file_operation, ru);
        }

        public void doStartUdp(Packet p, Socket s, IPEndPoint ipe)
        {
            PFile pa = (PFile)p;

            Application a = am.getApplication(pa.id_application);
            if (a == null)
            {
                lc.log("Application not found");
                return;
            }

            newTransfer(pa, s, ipe, a.InputPath, delegate(object sender, RunWorkerCompletedEventArgs e)
            {
                transferPending.Remove(pa.id_file_operation);
                a.start();
            });
        }

        public void doInstallUdp(Packet p, Socket s, IPEndPoint ipe)
        {
            PFile pa = (PFile)p;

            Application a = new Application(pa.id_application, pa.application, StatusType.STOPPED, am);

            newTransfer(pa, s, ipe, a.Path, delegate(object sender, RunWorkerCompletedEventArgs e)
            {
                transferPending.Remove(pa.id_file_operation);
                am.installApplication(a);
            });
        }

        public void doInstallTcp(Packet p, NetworkStream ns) 
        {
            PFile pf = (PFile)p;
            Application a = new Application(pf.id_application, pf.application, StatusType.STOPPED, am);
           
            ReceiveTCP rt = new ReceiveTCP(ns, a.Path);

            //riceve il file e lo salva
            try{
            rt.run();
            am.installApplication(a);
            }
            catch (Exception e) 
            {
                lc.log(e.Message);
            }
           
     
                
        }

        public void doStatusReq(Packet p, Socket s)
        {
            //faccio pacchetto pstatus
            PStatus ps = new PStatus(Command.STATUS_REP, p.id_operation, am.idClient, null, am.createStatus());
            
            //serializzo pacchetto e invio dati
            PacketSerializer ser = new PacketSerializer(ps);
            ser.serialize();
            s.Send(ser.res,ser.len,SocketFlags.None);                
        }

        public void doLog(Packet p, Socket s) 
        {
            PApplication pa = (PApplication)p;
            Application a = am.getApplication(pa.id_application);
            if (a == null)
            {
                lc.log("Application not found");
                return;
            }
            Packet pl = new Packet(Command.LOGREPLY, p.id_operation, am.idClient, null);
            
            PacketSerializer ps= new PacketSerializer(pl);
            byte[] b = new byte[ps.len];
            Buffer.BlockCopy(ps.res, 0, b, 0, ps.len);
            s.SendFile(a.LogPath, b, null, TransmitFileOptions.Disconnect);
        }


        public void tcpHandler(Packet p, Socket s, NetworkStream ns, IPEndPoint ipe)
        {

            switch (p.type)
            {
                case Command.STOP:
                    doStop(p);
                    break;
                case Command.UNINSTALL:
                    doUninstall(p);
                    break;

                case Command.START:
                    doStartTcp(p, ns);
                    break;

                case Command.INSTALL:
                    doInstallTcp(p, ns);
                    break;

                case Command.STATUS_REQ:
                    doStatusReq(p, s);
                    break;

                case Command.LOG:
                    doLog(p, s);
                    break;
            }
            s.Disconnect(false);
            s.Close();
        }

        private void fileSegment(Packet p, Socket s, IPEndPoint ipe)
        {
            
            PFileTransfer pft = (PFileTransfer) p;
            ReceiveUDP ru;
            transferPending.TryGetValue(pft.id_operation, out ru);

            if (ru != null)
            {
                ru.newSegment(pft);
            }
        }


        

        private List<ushort> operations; 


        public void udpHandler(Packet p, Socket s, IPEndPoint ipe)
        {
            if (p.type != Command.DISCOVERY && !isMine(p))
                return;

            bool present = operations.Contains(p.id_operation);

            switch (p.type)
            {
                case Command.STOP:
                    if (!present)
                    {
                        operations.Add(p.id_operation);
                        doStop(p);
                        sendAck(p, s, ipe);
                    }
                    
                    break;

                case Command.DISCOVERY:
                    sendHello(p,s,ipe);
                    break;

                case Command.UNINSTALL:
                    if (!present)
                    {
                        operations.Add(p.id_operation);
                        doUninstall(p);
                        sendAck(p, s, ipe);
                    }
                    break;

                case Command.START:
                    if (!present)
                    {
                        operations.Add(p.id_operation);
                        doStartUdp(p, s, ipe);
                        sendAck(p, s, ipe);
                    }
                    
                    break;

                case Command.FILEM:
                    fileSegment(p, s, ipe);
                    break;

                case Command.INSTALL:
                    if (!present)
                    {
                        operations.Add(p.id_operation);
                        doInstallUdp(p, s, ipe);
                        sendAck(p, s, ipe);
                    }
                    break;
            }
        }

    }
}
